/*******************************************************************************
 * Copyright (c) 2007, Geophysical Center RAS
 * Sergey Berezin, Dmitry Voitsekhovskiy
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms,
 * with or without modification, are permitted provided
 * that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * - Neither the name of the Geophysical Center RAS nor the names
 * of its contributors may be used to endorse or promote products
 * derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ******************************************************************************/
/* 
 * Simple syntax parser with C++ comments
 * 
 * Copyright (c) Sergey B. Berezin (s_berezin@cs.msu.su), 2002, 2003.
 *
 * You may freely distribute or modify this source code retaining the 
 * above copyright notice.
 *
 * This software is provided "as-is" and without guarantee or warrantee 
 * of any kind, expressed or implied :)
 */

using System;
using System.IO;
using System.Text;
using System.Diagnostics;

namespace VisualEsse.DataEngine {

	[Flags]
	public enum ParserMode {
		SignedNumbers      = 0x00000001,
		SingleLineComments = 0x00000002,
		MultiLineComments  = 0x00000004
	}

	public enum TokenType {
		Nothing = -1,
		String1,
		String2,
		Ident,
		Integer,
		Double,
		Char,
		Eof
	}

	public class ParserException : ApplicationException {
		public ParserException(string message) : base(message) 
		     { /* Nothing to do here */ }
		public ParserException() :  base() 
		     { /* Nothing to do here */ }
	}

	public interface IParserReader {
		int Read(); // Reads next byte from the stream
	}

	public class TextReaderWrapper : IParserReader {
		protected TextReader reader;

		public TextReaderWrapper(TextReader r) { 
			reader = r;
		}

		public int Read() {
			return reader.Read();
		}
	}

    public class AsciiStreamWrapper : IParserReader
    {
        protected Stream stream;

        public AsciiStreamWrapper(Stream s)
        {
            stream = s;
        }

        public int Read()
        {
            return stream.ReadByte();
        }
    }

	public class Parser {
		protected IParserReader reader = null;
		protected ParserMode mode = ParserMode.SignedNumbers|
			                        ParserMode.SingleLineComments|
			                        ParserMode.MultiLineComments;
		protected TokenType tokenType = TokenType.Nothing;
		protected string tokenText = "";
		protected int putback1 = -1;
		protected int putback2 = -1;
		protected double tokenDouble;
		protected int tokenInteger;

		public Parser(TextReader r) {
			reader = new TextReaderWrapper(r);
		}

        public Parser(Stream s)
        {
            reader = new AsciiStreamWrapper(s);
        }

		public string TokenText {
			get {
				return tokenText;
			}
		}

        public TokenType TokenType
        {
            get
            {
                return tokenType;
            }
        }

		public int IntegerValue {
			get {
				return tokenInteger;
			}
		}

		public double DoubleValue {
			get {
				return tokenDouble;
			}
		}

		public int GetPositiveInteger() {
			if(GetToken() != TokenType.Integer || tokenInteger <= 0)
				throw new ParserException("Expecting positive int");
			return tokenInteger;
		}

		public double GetDouble() {
			if(GetToken() == TokenType.Double)
				return tokenDouble;
			else if(tokenType == TokenType.Integer)
				return tokenInteger;
			else
				throw new ParserException("Expecting floating-point number");
		}

		public TokenType GetToken() {
			if(tokenType == TokenType.Eof)
				return TokenType.Eof;
			int ch = GetChar();
			while(ch != -1 && Char.IsWhiteSpace((char)ch))
				ch = GetChar();
			if(ch == '+' || ch == '-') {
				if((mode & ParserMode.SignedNumbers) != 0) {
					PutBack((char)ch);
					return GetNumber();
				} 
			} else if(Char.IsDigit((char)ch)) {
				PutBack((char)ch);
				return GetNumber();
			} else if(ch == '.') {
				int ch2 = GetChar();
				PutBack((char)ch2);
				if(Char.IsDigit((char)ch2)) {
					PutBack((char)ch);
					return GetNumber();
				}
            } else if (Char.IsLetter((char)ch) || ch == '_')
            {
                PutBack((char)ch);
                return GetIdent();
            }
			tokenText = ((char)ch).ToString(); // TODO: add parsing for strings
			tokenType = TokenType.Char;
			return tokenType;
		}

        protected TokenType GetIdent()
        {
            StringBuilder sb = new StringBuilder();
            int ch = GetChar();
            while(ch != -1 && (Char.IsLetter((char)ch) || Char.IsDigit((char)ch) || ((char)ch) == '_')) {
                sb.Append((char)ch);
                ch = GetChar();
            }
            if(ch != -1)
                PutBack((char)ch);
            tokenText = sb.ToString();
            tokenType = TokenType.Ident;
            return tokenType;
        }

		protected TokenType GetNumber() {
			StringBuilder buffer = new StringBuilder();
			bool floatingPoint = false;
			int ch = GetChar();
			if(ch == '+' || ch == '-') {
				buffer.Append((char)ch);
				ch = GetChar();
			}
			while(Char.IsDigit((char)ch)) {
				buffer.Append((char)ch);
				ch = GetChar();
			}
			if(ch == '.') {
				floatingPoint = true;
				buffer.Append((char)ch);
				ch = GetChar();
				while(Char.IsDigit((char)ch)) {
					buffer.Append((char)ch);
					ch = GetChar();
				}
			}
			if(ch == 'e' || ch == 'E') {
				floatingPoint = true;
				buffer.Append((char)ch);
				ch = GetChar();
				if(ch == '+' || ch == '-') {
					buffer.Append((char)ch);
					ch = GetChar();
				}
				if(!Char.IsDigit((char)ch))
					throw new ParserException("Syntax error parsing number");
				while(Char.IsDigit((char)ch)) {
					buffer.Append((char)ch);
					ch = GetChar();
				}
			}
			PutBack((char)ch);
			tokenText = buffer.ToString();
			System.Globalization.NumberFormatInfo fmt = new System.Globalization.NumberFormatInfo();
			fmt.NumberDecimalSeparator = ".";
			try {
				if(floatingPoint) {
					tokenType = TokenType.Double;
					tokenDouble = Double.Parse(tokenText,fmt);
				} else {
					tokenType = TokenType.Integer;
					tokenInteger = Int32.Parse(tokenText,fmt);
				}
			}
			catch(Exception) {
				throw new ParserException("Syntax error parsing number");
			}
			return tokenType;
		}

		protected int GetChar() {
			if(putback1 != -1) { // There are at least one character in putback buffer
				if(putback2 != -1) { // There are two characters in putback buffer
					char result2 = (char)putback2;
					putback2 = -1;
					return result2;
				}
				char result1 = (char)putback1;
				putback1 = -1;
				return result1;
			}
			int ch = reader.Read();
			if(ch == -1) // EOF
				return -1;
			if(ch == '/') { // Possible start of a comment
				int ch2 = reader.Read();
				if(ch2 == '/' && (mode & ParserMode.SingleLineComments) != 0) {
					while((ch = reader.Read()) != '\n' && ch != -1)
						;
				} else if(ch2 == '*' && (mode & ParserMode.MultiLineComments) != 0) {
					int count = 1; // allow nested comments
					while(true) {
						if((ch = reader.Read()) == -1) // EOF in comment
							throw new ParserException("EOF in comment");
						else if(ch == '*') {
							if((ch2 = reader.Read()) == -1) // EOF in comment
								throw new ParserException("EOF in comment");
							else if(ch2 == '/') 
								if(--count <= 0) {
									ch = ' ';
									break;
								}
						} else if(ch == '/') {
							if((ch2 = reader.Read()) == -1) // EOF in comment
								throw new ParserException("EOF in comment");
							else if(ch2 == '*')
								count++;
						}
					}
				} else  // Not a comment
					putback1 = ch2;
			}
			return ch;
		}

		protected void PutBack(char ch) {
			if(putback1 == -1)
				putback1 = ch;
			else if(putback2 == -1) {
				putback2 = putback1;
				putback1 = ch;
			} else 
				Debug.Assert(false); // putback buffer overflow
		}
	}
}